package dataflowscheme.engine.core;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import org.apache.log4j.Logger;

import dataflowScheme.Port;
import dataflowScheme.PortSet;
import dataflowScheme.SynchroPort;

/**
 * An event intended to perform Task execution. This is 
 * performed in a separate thread. 
 * 
 * @author <a href="mailto:misiak7@gmail.com">Michal Antolik</a>
 *
 */
public class TaskExecutionEvent implements IExecutionEvent, Runnable {

	private Logger log = Logger.getLogger(TaskExecutionEvent.class);
	
	/** a method to be used for execution call on referenced class */
	private final Method runMethod;
	
	/** source class on which processing will be called  */
	private final Class<?> sourceClass;
	
	/** number of portSet to be executed */
	private final int portset;
	
	/** encapsulated Task */
	private final CTask task;
	
	/** values received by portSet  */
	private final Map<String, Object> values;
	
	/** all methods of particular ports */
	private final Map<String, Method> portMethods;
	
	/** if statistics should be generated */
	private boolean prinStats;
	
	public TaskExecutionEvent(CTask task, int portset) {
		this.task = task;
		this.portset = portset;
		this.runMethod = task.getRunMethod();
		this.sourceClass = task.getSourceClass();
		this.values = task.getInputPortSetStruct(portset).getReceivedValues();
		this.portMethods = task.getPortMethods();
	}
	
	/* (non-Javadoc)
	 * @see dataflowscheme.engine.core.IExecutionEvent#execute()
	 */
	public Future<?> execute(ExecutionEventParser executionEventParser, ExecutorService threadExecutor, boolean prinStats) {
		this.prinStats = prinStats;
		
		//submit event to thread pool to be executed in separate thread 
		return threadExecutor.submit(this); 
	}

	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		try{
			if(prinStats){
				processWithStats();
			} else {
				processWithoutStats();
			}
		} catch(Exception e){
			log.error(msg("Unknown exception while processing"), e);
		}
	}
	
	private void processWithStats(){
			long startTime = Calendar.getInstance().getTimeInMillis();
		Integer returnValue = invoke();
			long semiTime = Calendar.getInstance().getTimeInMillis();
		task.receiveResult(returnValue);
			long endTime = Calendar.getInstance().getTimeInMillis();
		
			long serviceTime = endTime - semiTime;
			long processingTime = semiTime - startTime;
			ExecutionEPstats.getInstance().submit(serviceTime, processingTime, task.ePe.getName());
	}
	
	private void processWithoutStats(){
		Integer returnValue = invoke();
		task.receiveResult(returnValue);
	}
	
	/**
	 * process an execution by invoking 'run' method on an instance
	 * 
	 * @return portSet number selected in execution
	 */
	private Integer invoke(){
		dataflowScheme.PE ePe = task.getPE();
		
		log.debug(msg("starting executing"));
		
		PortSet ps = PE.findPortSetByID(ePe, portset);
		if(ps == null){
			log.error(msg("Bad number of portset("+ portset + ")"));
			return null;
		}
	
		Object sourceInstance = createSourceInstance();
		if(sourceInstance == null){
			return null;
		}
		
		if(!initializeSourcePorts(ps, sourceInstance, ePe)){
			return null;
		}
			
		try{
			return (Integer)runMethod.invoke(sourceInstance, portset);
		} catch (InvocationTargetException ite){
			log.error(msg("Invocation of main method failed."), ite);
		} catch (IllegalAccessException iae){
			log.error(msg("Method '" + runMethod.getName() + "' have to be public."),iae);
		}
		
		return null;
	}
	
	/**
	 * set values to an instance of referenced class
	 * 
	 * @param ps EMF ecore portset instance 
	 * @param sourceInstance instance of referenced class 
	 * @param ePe EMF ecore processing entity
	 * @return false if initialization failed 
	 */
	private boolean initializeSourcePorts(PortSet ps, Object sourceInstance, dataflowScheme.PE ePe){
		for (Port p : ps.getPort()){
			
			if (p instanceof SynchroPort){
				continue;
			}
			
			if(!values.containsKey(p.getName())){
				log.error(msg("Sth wrong, can not invoke portset method because necessary value doens't exist."));
				return false;
			}
			Object value = values.get(p.getName());
			
			Method m = portMethods.get(p.getName());
			try{
				m.invoke(sourceInstance, value);
			}catch(InvocationTargetException ite){
				log.error(msg("Invocation of port '" + p.getName() + "' failed."), ite);
				return false;
			} catch (IllegalArgumentException e) {
				log.error(msg("Method '" + m.getName() + "' of '" + ePe.getName() + "' has bad arguments."), e);
				return false;
			} catch (IllegalAccessException e) {
				log.error(msg("Method '" + m.getName() + "' of '" + ePe.getName() + "' have to be public."),e);
				return false;
			}
		}
		return true;
	}

	/**
	 * Created new instance of referenced class in each process of TaskExecutionEvent
	 * 
	 * @return new instance of referenced class
	 */
	private Object createSourceInstance(){
		Object sourceInstance = null;
		try{
			sourceInstance = sourceClass.newInstance();
			task.setSourceInstance(sourceInstance);
		} catch(IllegalAccessException ilae){
			log.error(msg("Cannot create instance of Task"), ilae);
		} catch(InstantiationException ie){
			log.error(msg("Cannot create instance of Task"), ie);
		}
		
		return sourceInstance;
	}
	
	private String msg(String msg){
		return task.ePe.getName() + " - " + msg; 
	}
}
